package com.zyl.file.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.event.ProgressEvent;
import com.amazonaws.event.ProgressEventType;
import com.amazonaws.event.ProgressListener;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AbortMultipartUploadRequest;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
import com.amazonaws.services.s3.model.ListPartsRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.PartListing;
import com.amazonaws.services.s3.model.PartSummary;
import com.amazonaws.services.s3.model.UploadPartRequest;
import com.amazonaws.services.s3.model.UploadPartResult;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.zyl.file.constant.FileConstants;
import com.zyl.file.constant.StateConstants;
import com.zyl.file.dto.FilePartInfoDTO;
import com.zyl.file.dto.ProgressMessageDTO;
import com.zyl.file.dto.SplitUploadDTO;
import com.zyl.file.dto.SplitUploadRecordDTO;
import com.zyl.file.dto.StFileDTO;
import com.zyl.file.dto.UploadDTO;
import com.zyl.file.exception.MyException;
import com.zyl.file.mapper.StEquipmentMapper;
import com.zyl.file.mapper.StFileMapper;
import com.zyl.file.platform.FileStorage;
import com.zyl.file.platform.FileStorageService;
import com.zyl.file.po.StEquipment;
import com.zyl.file.po.StFile;
import com.zyl.file.result.Result;
import com.zyl.file.result.ResultCode;
import com.zyl.file.service.LocalSplitFileService;
import com.zyl.file.util.AssertUtils;
import com.zyl.file.util.DateUtils;
import com.zyl.file.util.FileCheckUtils;
import com.zyl.file.util.FileEncryptUtils;
import com.zyl.file.util.FileUtils;
import com.zyl.file.util.FtpUtils;
import com.zyl.file.util.JsonUtils;
import com.zyl.file.util.Md5Utils;
import com.zyl.file.vo.SplitUploadVO;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.zyl.file.service.SplitUploadTaskService;
import com.zyl.file.vo.UploadSplitVO;
import com.zyl.file.websocket.WebSocketFrameHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Vector;
import java.util.stream.Collectors;

/**
 * 分片上传
 *
 * @author zyl
 * @Description
 * @since 2023/7/20 10:05
 */
@Service
@Slf4j
public class SplitUploadTaskServiceImpl implements SplitUploadTaskService {

    private static final Map<String,Object> ST_FILE_MAP = new HashMap<>();

    @Resource
    private Map<String, AmazonS3> amazonS3ClientMap;
    @Resource
    private StFileMapper stFileMapper;

    @Resource
    private FileStorageService fileStorageService;//注入实列

    @Value("${upload.chunk-size}")
    private Long chunkSize;
    @Value("${upload.lan-proxy-ip}")
    private String lanProxyIp;
    @Value("${upload.lan-proxy-port}")
    private String lanProxyPort;
    @Value("${upload.wan-proxy-ip}")
    private String wanProxyIp;
    @Value("${upload.wan-proxy-port}")
    private String wanProxyPort;
    @Resource
    private WebSocketFrameHandler webSocketHandler;

    @Resource
    private StEquipmentMapper stEquipmentMapper;

    /**
     * 加密密钥
     */
    @Value("${upload.encrypt.encrypt-key:8a08826984224746}")
    private String encryptKey;

    /**
     * 加密标识符（用于区分加密、未加密数据）
     */
    @Value("${upload.encrypt.encrypt-index:<ENCRYPTED>}")
    private String encryptIndex;

    /**
     * #国密（SM2）加密标识符（用于区分加密部分、未加密部分）
     */
    @Value("${upload.encrypt.sm2-encrypt-index:<SM2_ENCRYPTED>}")
    private static String sm2EncryptIndex = "<SM2_ENCRYPTED>";

    /*** -------------------------------------------------- 分片上传(前端请求OSS服务进行上传) --------------------------------------------------*/


    @Override
    public StFile getByIdentifier(String identifier, Long id) {
        List<StFile> stFiles = stFileMapper.selectList(new QueryWrapper<StFile>().lambda()
                .eq(StFile::getSourceFileMd5, identifier)
                .eq(ObjectUtil.isNotEmpty(id), StFile::getId, id)
                .orderByDesc(StFile::getCreateTime));
        if (!CollectionUtils.isEmpty(stFiles)) {
            return stFiles.get(0);
        }
        return null;
    }


    @Override
    public SplitUploadDTO getUploadInfo(SplitUploadVO splitUploadVO) {
        AssertUtils.isNull(splitUploadVO.getIsOpen(),"isOpen 不能为空");
        AssertUtils.isNull(splitUploadVO.getType(),"type 不能为空");
        AssertUtils.isNull(splitUploadVO.getEquipmentId(),"equipmentId 不能为空");
        AssertUtils.isNull(splitUploadVO.getBusiBatchNo(),"busiBatchNo 不能为空");
        AssertUtils.isNull(splitUploadVO.getFileName(),"fileName 不能为空");
        AssertUtils.isNull(splitUploadVO.getTotalSize(),"totalSize 不能为空");
        AssertUtils.isNull(splitUploadVO.getChunkSize(),"chunkSize 不能为空");
        AssertUtils.isNull(splitUploadVO.getSourceFileMd5(),"sourceFileMd5 不能为空");
        AssertUtils.isNull(splitUploadVO.getIsEncrypt(),"isEncrypt 不能为空");
        //获取上传进度
        SplitUploadDTO splitUploadDTO = getTaskInfo(splitUploadVO.getIdentifier(), splitUploadVO.getIsEncrypt(),splitUploadVO.getEquipmentId(),splitUploadVO.getId());
        if (splitUploadDTO == null) {
            //没有上传过就创建一个新的上传任务
            SplitUploadVO param = new SplitUploadVO();
            param.setEquipmentId(splitUploadVO.getEquipmentId());
            param.setBusiBatchNo(splitUploadVO.getBusiBatchNo());
            param.setFileName(splitUploadVO.getFileName());
            param.setIdentifier(splitUploadVO.getIdentifier());
            param.setTotalSize(splitUploadVO.getTotalSize());
            param.setChunkSize(splitUploadVO.getChunkSize());
            param.setSourceFileMd5(splitUploadVO.getSourceFileMd5());
            param.setIsEncrypt(splitUploadVO.getIsEncrypt());
            splitUploadDTO = initTask(param,splitUploadVO.getUserId());
        }
        //上传过进行断点续传
        //得到已上传的分片记录
        SplitUploadRecordDTO taskRecord = splitUploadDTO.getTaskRecord();
        //已上传的分片信息
        List<PartSummary> exitPartList = taskRecord.getExitPartList();
        List<Integer> partNumberList = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(exitPartList)){
            partNumberList = exitPartList.stream().map(PartSummary::getPartNumber).collect(Collectors.toList());
        }
        //总分片数量
        Integer chunkNum = taskRecord.getChunkNum();
        //文件md5
        String identifier = taskRecord.getSourceFileMd5();
        //文件id
        Long id = taskRecord.getId();
        //所有分片的上传地址
        List<String> urlList = new ArrayList<>();
        for (Integer partNumber = 1; partNumber <= chunkNum; partNumber++) {
            if (partNumberList.contains(partNumber)) {
                //该分片已经上传过
            } else {
                //没上传过的分片进行上传
                //1、获取上传路径
                String url = genPreSignUploadUrl(identifier, partNumber,splitUploadVO.getType(),splitUploadVO.getIsOpen(),id,splitUploadVO.getEquipmentId());
                urlList.add(url);
            }
        }
        splitUploadDTO.setUrlList(urlList);
        return splitUploadDTO;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(keys = {"#param.identifier"},acquireTimeout = 6000)
    public SplitUploadDTO initTask(SplitUploadVO param, Long userId) {
//        AssertUtils.isNull(userId,"userId不能为空");

        StFile byIdentifier = getByIdentifier(param.getIdentifier(), param.getId());
        if (!ObjectUtil.isNull(byIdentifier)) {
            throw new MyException(ResultCode.PARAM_ERROR, "本次上传有内容一样的文件，只能成功上传其中一个");
        }
        StFile task = new StFile();
        String fileName = param.getFileName();
        String fileName1 = IdUtil.randomUUID();
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
        String key = "";
        Date currentDate = new Date();
        key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), fileName1, suffix);
        task.setObjectKey(key);
        //获取创建上传任务
        FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(param.getEquipmentId()));
        fileStorage.initTask(task);
        //设置上传地址
        String url = fileStorage.getPath(key);
        task.setUrl(url);
        int chunkNum = (int) Math.ceil(param.getTotalSize() * 1.0 / param.getChunkSize());
        //判断是否加密
        String value = null;
        if (StateConstants.IS_ENCRYPT.equals(param.getIsEncrypt())){
//            value = paramApi.searchValueByKey(StateConstants.FILE_ENCRYPT_TYPE).getData().getValue();
        }
        task.setChunkNum(chunkNum)
                .setChunkSize(param.getChunkSize())
                .setSize(param.getTotalSize())
                .setFileMd5(param.getIdentifier())
                .setOriginalFilename(fileName)
                .setFilename(fileName1)
                .setExt(suffix)
                .setFilePath(task.getBucketName() + "/" + key)
                .setCreateUser(userId)
                .setBusiBatchNo(param.getBusiBatchNo())
                .setSourceFileMd5(param.getSourceFileMd5())
                .setEquipmentId(param.getEquipmentId())
                //设置是否加密、加密密钥、加密标识符
                .setIsEncrypt(param.getIsEncrypt())
                .setEncryptKey((StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? encryptKey : null) )
                .setEncryptIndex((StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? encryptIndex : sm2EncryptIndex));

        stFileMapper.insert(task);
        //缓存文件信息
        ST_FILE_MAP.put(task.getFileMd5(),task);
        log.info("文件信息插入缓存：{}", ST_FILE_MAP);
        SplitUploadRecordDTO dto = new SplitUploadRecordDTO();
        BeanUtil.copyProperties(task, dto);
        return new SplitUploadDTO()
                .setFinished(false)
                .setTaskRecord(dto)
                .setPath(fileStorage.getPath(key));
    }

    @Override
    public SplitUploadDTO getTaskInfo(String identifier, Integer isEncrypt,Long equipmentId ,Long id) {
        AssertUtils.isNull(identifier,"identifier不能为空");
        StFile task = null;
        List<StFile> stFileList = null;
        //根据md5获取文件
        stFileList = getByIdentifierAndId(identifier, id);
        if (CollectionUtil.isEmpty(stFileList)){
            return null;
        }else {
            //如果不能拿到文件id那就从没有上传完成的集合中根据时间顺序去最新的时间的那一条去断点续传
            task= stFileList.get(0);
        }
        if (!isSecondsUpload()){
            //不妙传
            if (ObjectUtil.isNotEmpty(id)){
                //前端传任务管理器中的未上传成功的文件id
                List<StFile> collect = stFileList.stream().filter(p -> Objects.equals(p.getId(), id)).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(collect)){
                    task = collect.get(0);
                }
            }
        }
        SplitUploadRecordDTO dto = new SplitUploadRecordDTO();
        BeanUtil.copyProperties(task, dto);
        //通过指定设备id，来确定存储路径/存储方式
        if (ObjectUtil.isEmpty(equipmentId)){
            long l2 = System.currentTimeMillis();
            StEquipment stEquipment = stEquipmentMapper.selectOne(new LambdaQueryWrapper<StEquipment>()
                    .eq(StEquipment::getId, task.getEquipmentId()));
            long l3 = System.currentTimeMillis();
            log.info("获取存储设备信息耗时：" + (l3 - l2) + " ms");
            equipmentId = stEquipment.getId();
        }
        FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(equipmentId));
        //获取已上传分片
        List<PartSummary> partSummaryList = fileStorage.getTaskInfo(task);
        SplitUploadDTO result = new SplitUploadDTO()
                .setFinished(true)
                .setTaskRecord(dto)
                .setPath(fileStorage.getPath(task.getObjectKey()));

        if (1!=task.getIsUploadOk()){
            //没上传完成
            result.setFinished(false).getTaskRecord().setExitPartList(partSummaryList);
        }else {
            //已经上传完成
            if (!isSecondsUpload()){
                //不妙传
                return null;
            }
        }
        return result;
    }

    private void extracted(List<PartSummary> exitPartList, File[] files) {
        if (null != files && files.length > 0) {
            for (File item : files) {
                PartSummary partSummary = new PartSummary();
                partSummary.setPartNumber(Integer.valueOf(item.getName().split("-")[0]));
                partSummary.setSize(item.length());
                exitPartList.add(partSummary);
            }
        }

    }


    @Override
    public String genPreSignUploadUrl(String identifier, Integer partNumber, String type, Boolean isOpen, Long id,Long equipmentId) {
        AssertUtils.isNull(identifier, "identifier: 文件唯一标识(MD5)不能为空");
        AssertUtils.isNull(partNumber, "partNumber: 上传某个分片标识不能为空");
        AssertUtils.isNull(type, "type不能为空");
        StFile task = (StFile) ST_FILE_MAP.get(identifier);
        if (ObjectUtil.isEmpty(task)){
            task = getByIdentifier(identifier, id);
        }
        AssertUtils.isNull(task, "分片任务不存在");
        //测试存储设备连接
        String newIP = null;
        String newPort = null;
        //区分移动端和pc端，移动端传值为0，pc端不传
        if (FileConstants.PC_FILE_UPLOADURL.equals(type)) {
            newIP = lanProxyIp;
            newPort = lanProxyPort;
        } else {
            newIP = wanProxyIp;
            newPort = wanProxyPort;
        }
        //通过指定设备id，来确定存储路径/存储方式
        //通过指定设备id，来确定存储路径/存储方式
        if (ObjectUtil.isEmpty(equipmentId)){
            StEquipment stEquipment = stEquipmentMapper.selectOne(new LambdaQueryWrapper<StEquipment>().eq(StEquipment::getId, task.getEquipmentId()));
            equipmentId = stEquipment.getId();
        }
        FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(equipmentId));
        StringBuffer nasUrl = new StringBuffer();
        nasUrl.append("http://" + newIP + ":" + newPort + "/" + "web-api/storage" + (isOpen ? "/api" : "") + "/storage/oss/splitUpload/uploadSplitOpen?");
        nasUrl.append("partNumber=" + partNumber);
        nasUrl.append("&equipmentId=" + task.getEquipmentId());
        nasUrl.append("&key=" + task.getObjectKey());
        nasUrl.append("&uploadId=" + task.getUploadId());
        nasUrl.append("&busiBatchNo=" + task.getBusiBatchNo());
        nasUrl.append("&fileId=" + task.getId());
        nasUrl.append("&fileName=" + task.getFilename() + "." + task.getExt());
        nasUrl.append("&identifier=" + task.getFileMd5());
        String taskUrl = task.getUrl();
        task.setUrl(nasUrl.toString());
        task.setConfigId(Long.valueOf(partNumber));
        String url = nasUrl.toString();
        task.setUrl(taskUrl);
        task.setConfigId(null);
        return url;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public StFile merge(String identifier, Boolean isFlat, Long id, Long equipmentId) {
        long start = System.currentTimeMillis();
        AssertUtils.isNull(isFlat, "isFlat：是否偏离矫正判断条件不能为空");
        StFile task = (StFile) ST_FILE_MAP.get(identifier);
        if (ObjectUtil.isEmpty(task)){
            task = getByIdentifier(identifier, id);
        }else {
            //清除缓存
            ST_FILE_MAP.remove(identifier);
        }
        if (task == null) {
            throw new RuntimeException("分片任务不存");
        }
        if (ObjectUtil.isEmpty(equipmentId)){
            StEquipment stEquipment = stEquipmentMapper.selectById(task.getEquipmentId());
            equipmentId = stEquipment.getId();
        }
        FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(equipmentId));
        fileStorage.merge(task);
        //偏离矫正
        if (FileCheckUtils.isImage(fileStorage.getFileStream(task.getObjectKey())) && isFlat) {
            log.info("进行了偏离矫正");
//            task = pictureCorrection(task, fileStorage);
        }
        //缓存
        InputStream fileStream = fileStorage.getFileStream(task.getObjectKey());
//        cacheCommonService.cacheFileAsync(fileStream,task.getUrl(),task.getId(),task.getExt(),task.getIsEncrypt(),task.getEncryptKey(),task.getEncryptIndex());
        task.setIsUploadOk(StateConstants.COMMON_ONE);
        stFileMapper.updateById(task);
//        SysFileDTO sysFileDTO = new SysFileDTO();
//        BeanUtils.copyProperties(task, sysFileDTO);
        long end = System.currentTimeMillis();
        log.info("合并文件总耗时（合并+合并后操作）：{}(毫秒)", end - start);
        return task;
    }

    /** -------------------------------------------------- 私有方法 --------------------------------------------------*/


    /*** -------------------------------------------------- 分片上传(后端请求OSS服务进行上传) --------------------------------------------------*/

    /**
     * S3后端分片上传
     * @param fileByte 文件
     * @param userId 用户id
     * @param stEquipmentId 设备id
     * @param fileName
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public StFileDTO useS3Upload(byte[] fileByte, Long userId, Long stEquipmentId, String fileName, String fileSource, String md5,Integer isEncrypt) {
        AssertUtils.isNull(stEquipmentId, "存储设备id不能为空");
        AssertUtils.isNull(fileByte, "文件字节流不能为空");
        AssertUtils.isNull(fileSource, "fileSource不能为空");
        int lastIndex = fileName.lastIndexOf(".");
        boolean b = lastIndex != -1 && lastIndex < fileName.length() - 1;
        AssertUtils.isTrue(!b, "文明名需包含后缀");
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
        AssertUtils.isNull(suffix, "文明名需包含后缀");
        UploadDTO uploadDTO = new UploadDTO();
        StFileDTO stFileDTO = new StFileDTO();
        InputStream inputStream = null;
        InputStream inputStreamMd5 = null;
        try (InputStream inputStream1 = new ByteArrayInputStream(fileByte);
             ByteArrayOutputStream baos = FileUtils.getByteOutputStream(inputStream1)){
            if (ObjectUtil.isEmpty(md5)){
                inputStreamMd5 = new ByteArrayInputStream(baos.toByteArray());
                md5 = Md5Utils.calculateMD5(inputStreamMd5);
            }
            String fileName1 = IdUtil.randomUUID();
            Date currentDate = new Date();
            String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), fileName1, suffix);
            inputStream = new ByteArrayInputStream(baos.toByteArray());
            long fileSize = inputStream.available();
            //判断是否加密
            String value = null;
            String encryptKey = null;
            String encryptIndex = null;
            if (StateConstants.IS_ENCRYPT.equals(isEncrypt)){
                //加密文件流
                long start = System.currentTimeMillis();
//                value = paramApi.searchValueByKey(StateConstants.FILE_ENCRYPT_TYPE).getData().getValue();
//                encryptKey= StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? this.encryptKey : null;
//                encryptIndex= StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? this.encryptIndex : sm2EncryptIndex;
//                inputStream = FileEncryptUtils.encrypt(inputStream, encryptKey, encryptIndex);
                long end = System.currentTimeMillis();
                log.info("加密耗时：" + (end - start) + " ms");
                if (inputStream != null){
                    fileSize = inputStream.available();
                }else {
                    log.info("加密失败");
                }
            }
            uploadDTO.setInputStream(inputStream);
            uploadDTO.setFileName(fileName);
            uploadDTO.setFileSize(fileSize);
            uploadDTO.setKey(key);
            uploadDTO.setFilePath(key);
            uploadDTO.setChunkSize(5*1024*1024L);
            uploadDTO.setIsEncrypt(isEncrypt);
            StFile stFile = new StFile();
            log.info("开始上传");
            FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(stEquipmentId));
            stFile = fileStorage.upload(uploadDTO);
            //保存文件信息
            stFile.setChunkNum(Math.toIntExact(fileSize / chunkSize))
                    .setChunkSize(chunkSize)
                    .setSize(fileSize)
                    .setFileMd5(md5)
                    .setOriginalFilename(fileName)
                    .setFilename(fileName1)
                    .setExt(suffix)
                    .setObjectKey(key)
                    .setCreateUser(userId)
                    .setEquipmentId(stEquipmentId)
                    .setSourceFileMd5(md5)
                    .setBusiBatchNo(UUID.randomUUID().toString())
                    .setFileSource(fileSource)
                    .setIsUploadOk(FileConstants.FILE_FINISH)
                    //设置是否加密、加密密钥、加密标识符
                    .setIsEncrypt(isEncrypt)
                    .setEncryptKey(encryptKey)
                    .setEncryptIndex(encryptIndex);
            stFileMapper.insert(stFile);
            BeanUtils.copyProperties(stFile, stFileDTO);
            log.info("上传成功");
            //缓存
            InputStream fileStream = fileStorage.getFileStream(stFile.getObjectKey());
//            cacheCommonService.cacheFileAsync(fileStream,stFile.getUrl(),stFile.getId(),stFile.getExt(),stFile.getIsEncrypt(),stFile.getEncryptKey(),stFile.getEncryptIndex());
        } catch (Exception e) {
            log.error("异常描述",e);
            throw new RuntimeException(e.getMessage(),e);
        }finally {
            if (ObjectUtil.isNotEmpty(inputStream)){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("关闭文件流失败",e);
                    throw new RuntimeException(e.getMessage(),e);
                }
            }
            if (ObjectUtil.isNotEmpty(inputStreamMd5)){
                try {
                    inputStreamMd5.close();
                } catch (IOException e) {
                    log.error("关闭文件流失败",e);
                    throw new RuntimeException(e.getMessage(),e);
                }
            }
        }
        return stFileDTO;
    }

    @Override
    public StFileDTO useS3Upload(MultipartFile multipartFile, Long userId, Long stEquipmentId, String fileName, String fileSource, String md5, Integer isEncrypt) {
        AssertUtils.isNull(stEquipmentId, "存储设备id不能为空");
        AssertUtils.isNull(multipartFile, "文件流不能为空");
        AssertUtils.isNull(fileSource, "fileSource不能为空");
        if (ObjectUtil.isEmpty(fileName)){
            fileName = multipartFile.getOriginalFilename();
        }
        int lastIndex = fileName.lastIndexOf(".");
        boolean b = lastIndex != -1 && lastIndex < fileName.length() - 1;
        AssertUtils.isTrue(!b, "文明名需包含后缀");
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
        UploadDTO uploadDTO = new UploadDTO();
        StFileDTO stFileDTO = new StFileDTO();
        try {
            if (ObjectUtil.isEmpty(md5)){
                md5 = Md5Utils.calculateMD5(multipartFile.getInputStream());
            }
            ObjectMetadata objectMetadata = new ObjectMetadata();
            String fileName1 = IdUtil.randomUUID();
            Date currentDate = new Date();
            String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), fileName1, suffix);
            String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
            objectMetadata.setContentType(contentType);
            long fileSize = multipartFile.getSize();
            //判断是否加密
            String value = null;
            String encryptKey = null;
            String encryptIndex = null;
            InputStream inputStream = multipartFile.getInputStream();
//            if (StateConstants.IS_ENCRYPT.equals(isEncrypt)){
//                //加密文件流
//                long start = System.currentTimeMillis();
//                value = paramApi.searchValueByKey(StateConstants.FILE_ENCRYPT_TYPE).getData().getValue();
//                encryptKey= StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? this.encryptKey : null;
//                encryptIndex= StateConstants.FILE_ENCRYPT_TYPE_AES.equals(value) ? this.encryptIndex : sm2EncryptIndex;
//                inputStream = FileEncryptUtils.encrypt(inputStream, encryptKey, encryptIndex);
//                long end = System.currentTimeMillis();
//                log.info("加密耗时：" + (end - start) + " ms");
//                if (inputStream != null){
//                    fileSize = inputStream.available();
//                }else {
//                    log.info("加密失败");
//                }
//            }
            uploadDTO.setInputStream(inputStream);
            uploadDTO.setFileName(multipartFile.getOriginalFilename());
            uploadDTO.setFileSize(fileSize);
            uploadDTO.setKey(key);
            uploadDTO.setFilePath(key);
            uploadDTO.setChunkSize(5*1024*1024L);
            uploadDTO.setIsEncrypt(isEncrypt);
            StFile stFile = new StFile();
            log.info("开始上传");
            FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(stEquipmentId));
            stFile = fileStorage.upload(uploadDTO);
            //保存文件信息
            stFile.setChunkNum(Math.toIntExact(fileSize / chunkSize))
                    .setChunkSize(chunkSize)
                    .setSize(fileSize)
                    .setFileMd5(md5)
                    .setOriginalFilename(fileName)
                    .setFilename(fileName1)
                    .setExt(suffix)
                    .setObjectKey(key)
                    .setCreateUser(userId)
                    .setEquipmentId(stEquipmentId)
                    .setSourceFileMd5(md5)
                    .setBusiBatchNo(UUID.randomUUID().toString())
                    .setFileSource(fileSource)
                    .setIsUploadOk(FileConstants.FILE_FINISH)
                    //设置是否加密、加密密钥、加密标识符
                    .setIsEncrypt(isEncrypt)
                    .setEncryptKey(encryptKey)
                    .setEncryptIndex(encryptIndex);
            stFileMapper.insert(stFile);
            BeanUtils.copyProperties(stFile, stFileDTO);
            log.info("上传成功");
            //缓存
            InputStream fileStream = fileStorage.getFileStream(stFile.getObjectKey());
//            cacheCommonService.cacheFileAsync(fileStream,stFile.getUrl(),stFile.getId(),stFile.getExt(),stFile.getIsEncrypt(),stFile.getEncryptKey(),stFile.getEncryptIndex());
        } catch (Exception e) {
            log.error("异常描述",e);
            throw new RuntimeException(e.getMessage(),e);
        }
        return stFileDTO;
    }

    /**
     * 取消上传
     * @param md5
     * @param stEquipmentId
     */
    @Override
    public void cancelFileUpload(String md5, String stEquipmentId) {
        AssertUtils.isNull(md5, "参数错误,文件的md5为空");
        AssertUtils.isNull(stEquipmentId, "参数错误,设备id为空");
        StFile stFile = getByIdentifier(md5,null);
        AssertUtils.isNull(stFile, "参数错误");
        AssertUtils.isNull(stFile.getBucketName(), "参数错误,所属桶名为空");
        AssertUtils.isNull(stFile.getObjectKey(), "参数错误，桶下的文件路径为空");
        AssertUtils.isNull(stFile.getUploadId(), "参数错误,上传id为空");
        AmazonS3 amazonS3 = amazonS3ClientMap.get(stEquipmentId);
        AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(stFile.getBucketName(), stFile.getObjectKey(), stFile.getUploadId());
        amazonS3.abortMultipartUpload(abortRequest);
        log.info("文件上传已取消,已经上传的分片已被删除");
    }

    @Override
    public FilePartInfoDTO uploadSplit(UploadSplitVO uploadSplitVO) {
        StFile task = (StFile) ST_FILE_MAP.get(uploadSplitVO.getIdentifier());
        if (ObjectUtil.isEmpty(task)){
            task = getByIdentifier(uploadSplitVO.getIdentifier(), uploadSplitVO.getFileId());
        }
        uploadSplitVO.setIsEncrypt(task.getIsEncrypt());
        uploadSplitVO.setEncryptKey(task.getEncryptKey());
        uploadSplitVO.setEncryptIndex(task.getEncryptIndex());
        FileStorage fileStorage = fileStorageService.getFileStorage(String.valueOf(uploadSplitVO.getEquipmentId()));
        return fileStorage.uploadSplit(uploadSplitVO);
    }
    /**
     * -------------------------------------------------- 私有方法 --------------------------------------------------
     */

    /**
     * 偏离矫正
     * @param task 文件对象
     * @param fileStorage 文件存储
     * @return StFile
     */
    private StFile pictureCorrection(StFile task, FileStorage fileStorage) {
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//        InputStream inputStreamFromUrl = null;
//        inputStreamFromUrl = fileStorage.getFileStream(task.getObjectKey());
//        //读取InputStream并转换为OpenCV的Mat对象
//        byte[] byteArray = new byte[0];
//        byteArray = FileUtils.read(inputStreamFromUrl);
//        // 使用OpenCV将字节数组转换为Mat
//        Mat m = Imgcodecs.imdecode(new MatOfByte(byteArray), Imgcodecs.IMREAD_COLOR);
//
//        //偏离矫正
//        Mat mat = RectifyImageUtils.imgCorrection(m, byteArray);
//        //保存的二进制数据
//        MatOfByte b = new MatOfByte();
//        //Mat转换成二进制数据。.png表示图片格式，格式不重要，基本不会对程序有任何影响。
//        Imgcodecs.imencode(".png", mat, b);
//        //二进制数据转换成Image
//        ByteArrayInputStream inputStream = new ByteArrayInputStream(b.toArray());
//        AssertUtils.isNull(inputStream, "偏离矫正有误");
//        Date currentDate = new Date();
//        String uuid = IdUtil.randomUUID();
//        String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), uuid, task.getExt());
//        UploadDTO uploadDTO = new UploadDTO()
//                .setInputStream(inputStream)
//                .setKey(key)
//                .setFileSize((long) inputStream.available())
//                .setFilePath(task.getObjectKey())
//                .setChunkSize(chunkSize);
//        //替换文件
//        StFile stFile = fileStorage.replaceFile(uploadDTO);
//        task.setUrl(stFile.getUrl())
//                .setFilePath(stFile.getFilePath())
//                .setFilename(uuid)
//                .setUploadId(stFile.getUploadId())
//                .setObjectKey(key);
        return task;
    }

    /**
     * 进度条
     * @param uploadedBytes
     * @param totalBytes
     * @param stFile
     * @return
     */
    private ProgressListener progressListener(long uploadedBytes, long totalBytes, StFile stFile) {
        final long[] uploadedByte = {uploadedBytes};
        return new ProgressListener() {
            @Override
            public void progressChanged(ProgressEvent progressEvent) {
                ProgressEventType eventType = progressEvent.getEventType();
                if (eventType == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) {
                    // 更新已上传字节数
                    uploadedByte[0] += progressEvent.getBytesTransferred();
                    // 计算上传进度百分比
                    int progressPercentage = (int) ((uploadedByte[0] * 100) / totalBytes);
                    log.info("已上传:{}",progressPercentage);
                    // 将上传进度信息传递给前端
                    // 推送进度信息给WebSocket连接
                    webSocketHandler.send(stFile.getCreateUser(), createProgressMessage(progressPercentage, stFile));
                } else if (progressEvent.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT){
                    log.info("上传完成");
                    // 推送进度信息给WebSocket连接
                    webSocketHandler.send(stFile.getCreateUser(), String.valueOf(100));
                } else if (progressEvent.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) {
                    log.info("上传开始");
                }
            }
        };
    }

    /**
     * 发送WebSocket的信息
     * @param progressPercentage
     * @param stFile
     * @return
     */
    private String createProgressMessage(int progressPercentage, StFile stFile) {
        ProgressMessageDTO progressMessage = new ProgressMessageDTO();
        progressMessage.setProgress(progressPercentage);
        progressMessage.setMd5(stFile.getSourceFileMd5());
        progressMessage.setNewFileName(stFile.getOriginalFilename());
        progressMessage.setFileId(stFile.getId());
        // 创建进度信息的 JSON 字符串
        String progressMessages = JsonUtils.toJSONString(progressMessage);
        return progressMessages;
    }

    /**
     * 加密文件
     *
     * @param task
     * @return
     */
    public StFile fileEncrypt(StFile task, FileStorage fileStorage) {
        InputStream inputStreamFromUrl = null;
        inputStreamFromUrl = fileStorage.getFileStream(task.getObjectKey());
        AssertUtils.isNull(inputStreamFromUrl, "文件加密有误：获取源文件流出错");
        //调用加密方法
        InputStream encryptStream = FileEncryptUtils.encrypt(inputStreamFromUrl, task.getEncryptKey(), task.getEncryptIndex());
        AssertUtils.isNull(encryptStream, "文件加密有误：加密文件流出错");
        Date currentDate = new Date();
        String uuid = IdUtil.randomUUID();
        String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), uuid, task.getExt());
        try {
            UploadDTO uploadDTO = new UploadDTO()
                    .setInputStream(encryptStream)
                    .setKey(key)
                    .setFileSize(encryptStream.available())
                    .setFilePath(task.getObjectKey())
                    .setChunkSize(chunkSize);
            log.info("将加密后的文件重新上传");
            long start = System.currentTimeMillis();
            StFile stFile = fileStorage.replaceFile(uploadDTO);
            task.setUrl(stFile.getUrl())
                    .setFilePath(stFile.getFilePath())
                    .setFilename(uuid)
                    .setUploadId(stFile.getUploadId())
                    .setObjectKey(key);
            long end = System.currentTimeMillis();
            log.info("将加密后的文件重新上传耗时：{}(毫秒)", end - start);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return task;
    }


    /**
     * 判断时候进行秒传
     * @return Result
     */
    private Boolean isSecondsUpload(){
//        Result<SysParamDTO> paramDtoResult = paramApi.searchValueByKey("FILE_UPLOAD_SECOND_PASS_SWITCH");
//        if (ObjectUtil.isNotEmpty(paramDtoResult.getData()) && "1".equals(paramDtoResult.getData().getValue())){
//            return true;
//        }
        return false;
    }

    /**
     * 获取文件对象
     * @param identifier md5
     * @param id id
     * @return StFile
     */
    private List<StFile> getByIdentifierAndId(String identifier, Long id) {
        List<StFile> stFiles = stFileMapper.selectList(new QueryWrapper<StFile>().lambda()
                .eq(StFile::getSourceFileMd5, identifier)
                .eq(ObjectUtil.isNotEmpty(id), StFile::getId, id)
                .orderByDesc(StFile::getCreateTime));
        if (!CollectionUtils.isEmpty(stFiles)) {
            return stFiles;
        }
        return null;
    }
}
